Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolver Light Theme And Kibana Integration #67859

Merged
merged 15 commits into from
Jun 13, 2020

Conversation

michaelolo24
Copy link
Contributor

@michaelolo24 michaelolo24 commented Jun 1, 2020

Summary

This PR introduces the light theme resolver as well as the functionality that hooks into the kibana theming. All former resolver functionality is retained with the addition of elapsed time displayed between the process nodes (ranging from milliseconds to years, though the latter probably is unlikely to actually occur). All other changes are primarily aesthetic and are listed below.

All colors are aligned with the dark and light themes colors provided in eui here: (https://elastic.github.io/eui/#/guidelines/colors)

  1. Event buttons as well as description text disappears as we zoom out to give a better sense of space within the resolver. A tooltip is provided for the process name buttons when the width shortens at the most zoomed out level.

  2. Event action buttons are displayed at the default scale on resolver load

  3. Nodes are centered on the edge line and action buttons retain a central position relative to the node.

  4. Panning controls are aligned with the themes

  5. Event detail dropdown now uses the eui popover

  6. Process nodes now have a hover state

  7. Process node active outline now aligns with the button color

1. Light theme using current v7 light theme colors (really only relevant to button color)

Screen Shot 2020-06-01 at 9 51 38 AM

**2. Action buttons hidden
Screen Shot 2020-06-11 at 4 28 57 PM

**3. Description text hidden

Screen Shot 2020-06-11 at 4 27 52 PM

4. Light theme using new amsterdam light theme (once again relevant here to only button color)

Screen Shot 2020-06-01 at 1 17 21 PM

**5. Dark theme

Screen Shot 2020-06-01 at 1 16 27 PM

Checklist

Delete any items that are not applicable to this PR.

For maintainers

@michaelolo24 michaelolo24 requested a review from bkimmel June 1, 2020 16:06
@michaelolo24 michaelolo24 added Feature:Endpoint Elastic Endpoint feature Feature:Resolver Security Solution Resolver feature release_note:skip Skip the PR/issue when compiling release notes Team:Endpoint Data Visibility Team managing the endpoint resolver v7.9.0 v8.0.0 labels Jun 1, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/endpoint-app-team (Feature:Resolver)

@elasticmachine
Copy link
Contributor

Pinging @elastic/endpoint-data-visibility-team (Team:Endpoint Data Visibility)

type ResolverColorNames =
| 'ok'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ 🆒 Nice job cleaning up this 🌈 🗑️ .

@michaelolo24
Copy link
Contributor Author

Planning another walk through with @lindseypoli to confirm changes on Friday

Copy link
Contributor

@oatkiller oatkiller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking nice. Put some questions and comments for ya

type DateFormat = Date | number | string;
export const getRelativeTimeDifference = (startDate: DateFormat, endDate: DateFormat): string => {
// Set the threshold above which pluralization takes place https://momentjs.com/docs/#/customization/relative-time-threshold/
moment.relativeTimeThreshold('s', 60); // Least number of seconds to be considered minute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never used moment before, so excuse me if this is a dumb question. does this mutate a global 'moment' reference?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, not dumb, and you're right. I'm looking into the FormattedRelative component as a general replacement for this now, but I may either end up writing my own function or just sticking with moment as it doesn't look like FormattedRelative provides a simple way to retrieve elapsed time '3 seconds ago' => '3 seconds'. I can check with @lindseypoli & @marrasherrier, if they would be okay with that more relative phrasing, but that may be more confusing to the user.

import { i18n } from '@kbn/i18n';

type DateFormat = Date | number | string;
export const getRelativeTimeDifference = (startDate: DateFormat, endDate: DateFormat): string => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this function slow? do we run it on every frame, or are we memoizing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memoized in the processNodePositionsAndEdgeLineSegments selector which I believe only runs on the initial load of the process tree.

const [numStr] = elapsedTime.split(' ');
const weekVal = Math.floor(parseInt(numStr, 10) / 7);
if (weekVal > 0) {
elapsedTime = `${weekVal} ${weekVal > 1 ? 'weeks' : ' week'}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this use i18n? different locales might wannt to reperenst this differently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a search and found

import { FormattedRelative } from '@kbn/i18n/react';

Would that approach work here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, it should be using i18n. And thanks for that. I may be able to go with that, but it's gonna depend on how design feels about the subtle change in phrasing. It might actually be better for me to go with FormattedNumber or FormattedPlural for the way we have it designed at the moment

/**
* An array of vectors2 forming an polyline. Used to connect process nodes in the graph.
* A tuple of 2 vector2 points and optional EdgeLineMetadata forming a polyline. Used to connect process nodes in the graph and display relevant information.
Copy link
Contributor

@oatkiller oatkiller Jun 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. why is edgelinemetadata optional?
  2. The edge line was represented as an array of vectors since it was just a geometric concept. Now that there is other data involved, how about a shape like this:
type EdgeLine = {
  points: [Vector2, Vector2];
  elapsedTime: number;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I'm guessing you meant optional? If so, it's because midway lines don't have any of that information

  2. see below comment

* Values shared between two vertices joined by an edge line.
*/
export interface EdgeLineMetadata {
elapsedTime?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If elaspedTime requires formatting, it may end up requiring DOM elements and the like. For that reason, perhaps it should be calculated in the react code.

If you went down that path, here's some ideas:
Instead of storing the (formatted) elasped time in state (or even storing the points) you could store the processes that the edge line connects:

type EdgeLine = {
  from: ResolverEvent;
  to: ResolverEvent;
}

Now the EdgeLine just represents the edge in the mathy-graphy sense.
Then you could add selectors like:


export function pointsForEdge(edgeLine: EdgeLine) {
  return [positionForNode(edgeLine.from), positionForNode(edgeLine.to) ]
}

export function relativeTimeForEdge(edgeLine: EdgeLine) {
  // im assuming that eventTimestamp is ms since epoch, if not, that conversion would be needed.
  return eventTimestamp(edgeLine.to) - eventTimestamp(edgeLine.from)
}

Then in the react code, you could get the points from poinntsForEdge by passing the edge (object reference itself.)

For rendering the time, you could do something like

<FormattedRelative value={relativeTimeForEdge(edgeLine)} />

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, and is definitely more extensible. I'll look into implementing it.

const isShowingDescriptionText = magFactorX >= 0.55;

const nodeXOffsetValue = isZoomedOut
? -0.143413 + (-magFactorX * (1 - magFactorX)) / 10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain these magic numbers, and this expression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just modified the initial numbers set on 254 and 255 to better align on the graph. The function is just another magFactor dependent scale to help keep the position of the node centered as you scaled down and the event drop downs are removed from view.

? -0.53684 + (-magFactorX * 0.2 * (1 - magFactorX)) / magFactorX
: -0.53684;

const actionsBaseYOffsetPct = 5;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can rename it and add a description. It sets the top % offset for the process buttons to keep them aligned as the resolver scales down

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessarily something you need to take on, but it might be good for use to try moving display logic into CSS where possible.

@@ -360,17 +396,17 @@ const ProcessEventDotComponents = React.memo(
return [];
}
// If we have entries to show, map them into options to display in the selectable list
return Object.entries(relatedStats).map((statsEntry) => {
const displayName = getDisplayName(statsEntry[0]);
return Object.entries(relatedStats).map(([name, value]) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this is older code. Any idea why we're using Object.entries here? We control the data structure (for the most part) so maybe we could change this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, but I can update it to just a single iteration. 👍

id={labelId}
size="s"
style={{
maxHeight: `${Math.min(26 + magFactorX * 3, 32)}px`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain this expression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the max-height of the button at 32, but it allows the sides of the button to better line up with the edge of the processNode

@@ -123,6 +134,8 @@ const NodeSubMenuComponents = React.memo(
[menuAction]
);

const closePopover = () => setMenuOpen(false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should use useCallback here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can throw it in one, but it may be counter productive since it really doesn't have any dependencies. There was this article I read a little while ago from Kent Dodds that goes into it a bit: https://kentcdodds.com/blog/usememo-and-usecallback

Copy link
Contributor

@oatkiller oatkiller Jun 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add it to my reading list. Can you summarize here? I'll elaborate on my reasoning. Maybe it's wrong:

Any time this component renders, the closePopover reference will be new. The EuiPopover instance below is passed closePopover. If the EuiPopover component re-renders when its properties change, it will be guaranteed to rerender. By passing a memoized version of the closePopover function, we can avoid forcing EuiPopover to rerender.

If we force EuiPopover to rerender, then readers of this code will need to understand the performance complexity of EuiPopover in order to understand the perf complexity of this function (not meaning the strict technical definitions of those terms here.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read the article. This section talks about the use case I'm going for: https://kentcdodds.com/blog/usememo-and-usecallback#reactmemo-and-friends

More on closePopover:
In our particular case, EuiPopover fails to use React.memo or PureComponent. It will rerender on every frame. For that reason, I think you're correct: there is no performance benefit to using useCallback for closePopover. I think you're justified in leaving it out.

That being said, I would argue that EuiPopover should use PureComponent or useMemo. As it is, those components will rerender on every frame. Even the memory allocated for 'jsx' (e.g. just stuff like <div />) is substantial at that scale.

Copy link
Contributor

@oatkiller oatkiller Jun 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also re: the article. While I agree w/ a lot of what is said there. The author suggests not to use React.memo. I would argue the opposite. In fact I'd argue that React shouldn't have any other way to create components.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also re: the article. While I agree w/ a lot of what is said there. The author suggests not to use React.memo. I would argue the opposite. In fact I'd argue that React shouldn't have any other way to create components.

Completely agree with this

@michaelolo24 michaelolo24 marked this pull request as ready for review June 11, 2020 19:55
@michaelolo24 michaelolo24 requested review from a team as code owners June 11, 2020 19:55
@michaelolo24 michaelolo24 changed the title WIP: transition dropdowns and cleanup Light Theme And Kibana Integration Jun 11, 2020
@michaelolo24 michaelolo24 changed the title Light Theme And Kibana Integration Resolver Light Theme And Kibana Integration Jun 11, 2020

import { DurationDetails, DurationTypes } from '../types';

export const getFriendlyElapsedTime = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a comment like

/**
 * Returns something. Used somehow.
 */

and would you mind adding an explicit return type.

Also, can you explain the reasoning for accepting either numbers or strings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventTimestamp depending on endgame event or current event will return either a string or number.

export function eventTimestamp(event: ResolverEvent): string | undefined | number {

<>
<linearGradient
id={PaintServerIds.terminatedProcessCube}
x1="10.5206"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❔ just curious - why are we using different US offsets for the light mode gradients?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the same thing, but just following the exported SVG I was provided 🤔

Copy link
Contributor

@bkimmel bkimmel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a couple 🔴 s on here I'd like to see answered but I'm giving a provisional thumb anyway. Maybe make issues for them if you can't address before merging. Really great job making this stuff look good here.

>
<FormattedMessage
id="xpack.securitySolution.endpoint.resolver.elapsedTime"
defaultMessage="{duration} {durationType}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so in order to translate, you'd need to use a select here I guess? like:

"{durationType, select, day {días} weeks {semanas}}"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was my thought that FormattedMessage actually translates this internally from what I see here https://formatjs.io/docs/react-intl/components/#formattedmessage and in other parts of Kibana

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@michaelolo24 michaelolo24 merged commit 04b8a49 into elastic:master Jun 13, 2020
@michaelolo24 michaelolo24 deleted the light-theme-resolver branch June 13, 2020 01:56
michaelolo24 added a commit to michaelolo24/kibana that referenced this pull request Jun 13, 2020
# Conflicts:
#	x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts
#	x-pack/plugins/security_solution/public/resolver/view/index.tsx
#	x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
michaelolo24 added a commit to michaelolo24/kibana that referenced this pull request Jun 13, 2020
# Conflicts:
#	x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts
#	x-pack/plugins/security_solution/public/resolver/view/index.tsx
#	x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jun 15, 2020
* master: (91 commits)
  [Search][BUG] Call wrong search strategy recursively in async search (elastic#69116)
  [Observability] Create context container to enable Observability plugin registry function (elastic#68642)
  Rename space id for disabled index pattern test (elastic#68990)
  skip flaky suite (elastic#63339)
  Resolver Light Theme And Kibana Integration (elastic#67859)
  [kbn/dev-utils] expose public tooling_log module (elastic#68868)
  index pattern(s) take dependencies as object (elastic#69055)
  include ci-stats metrics in pr comment (elastic#68563)
  Bump webpack packages (elastic#68716)
  [Uptime] Fixed metric query broken because of missing mapping (elastic#68999)
  Added cloud as an optional dependency (elastic#69050)
  Fixed all external links (elastic#68614)
  [DOCS] Reorganizes doc nav to match new Kibana nav (elastic#69069)
  [Endpoint] Using the stats provided by the backend for resolver UI (elastic#68577)
  [DOCS] Removees 8.0 from Upgrade Assistant docs (elastic#69067)
  [ML] Fix cloud deployment ID check (elastic#68695)
  [DOCS] Move metrics app content to metrics monitoring guide (elastic#69033)
  Add ingest manager topic to docs (elastic#68980)
  [SECURITY SOLUTION] EMT-401: add policy data to metadata and fix tests (elastic#68582)
  [DOCS] Fixes POST request for saved objects (elastic#69036)
  ...
gmmorris added a commit to gmmorris/kibana that referenced this pull request Jun 15, 2020
* master: (60 commits)
  Re-enable mistakenly skipped tests. (elastic#69123)
  [Search][BUG] Call wrong search strategy recursively in async search (elastic#69116)
  [Observability] Create context container to enable Observability plugin registry function (elastic#68642)
  Rename space id for disabled index pattern test (elastic#68990)
  skip flaky suite (elastic#63339)
  Resolver Light Theme And Kibana Integration (elastic#67859)
  [kbn/dev-utils] expose public tooling_log module (elastic#68868)
  index pattern(s) take dependencies as object (elastic#69055)
  include ci-stats metrics in pr comment (elastic#68563)
  Bump webpack packages (elastic#68716)
  [Uptime] Fixed metric query broken because of missing mapping (elastic#68999)
  Added cloud as an optional dependency (elastic#69050)
  Fixed all external links (elastic#68614)
  [DOCS] Reorganizes doc nav to match new Kibana nav (elastic#69069)
  [Endpoint] Using the stats provided by the backend for resolver UI (elastic#68577)
  [DOCS] Removees 8.0 from Upgrade Assistant docs (elastic#69067)
  [ML] Fix cloud deployment ID check (elastic#68695)
  [DOCS] Move metrics app content to metrics monitoring guide (elastic#69033)
  Add ingest manager topic to docs (elastic#68980)
  [SECURITY SOLUTION] EMT-401: add policy data to metadata and fix tests (elastic#68582)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Endpoint Elastic Endpoint feature Feature:Resolver Security Solution Resolver feature release_note:skip Skip the PR/issue when compiling release notes Team:Endpoint Data Visibility Team managing the endpoint resolver v7.9.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants